// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/proxy/proxy_resolver_v8_tracing_wrapper.h"

#include <string>

#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/values.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/dns/host_cache.h"
#include "net/dns/mock_host_resolver.h"
#include "net/log/net_log.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_entry.h"
#include "net/log/test_net_log_util.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_resolver_error_observer.h"
#include "net/test/event_waiter.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace net {

namespace {

    class ProxyResolverV8TracingWrapperTest : public testing::Test {
    public:
        void TearDown() override
        {
            // Drain any pending messages, which may be left over from cancellation.
            // This way they get reliably run as part of the current test, rather than
            // spilling into the next test's execution.
            base::RunLoop().RunUntilIdle();
        }
    };

    scoped_refptr<ProxyResolverScriptData> LoadScriptData(const char* filename)
    {
        base::FilePath path;
        PathService::Get(base::DIR_SOURCE_ROOT, &path);
        path = path.AppendASCII("net");
        path = path.AppendASCII("data");
        path = path.AppendASCII("proxy_resolver_v8_tracing_unittest");
        path = path.AppendASCII(filename);

        // Try to read the file from disk.
        std::string file_contents;
        bool ok = base::ReadFileToString(path, &file_contents);

        // If we can't load the file from disk, something is misconfigured.
        EXPECT_TRUE(ok) << "Failed to read file: " << path.value();

        // Load the PAC script into the ProxyResolver.
        return ProxyResolverScriptData::FromUTF8(file_contents);
    }

    std::unique_ptr<ProxyResolverErrorObserver> ReturnErrorObserver(
        std::unique_ptr<ProxyResolverErrorObserver> error_observer)
    {
        return error_observer;
    }

    std::unique_ptr<ProxyResolver> CreateResolver(
        NetLog* net_log,
        HostResolver* host_resolver,
        std::unique_ptr<ProxyResolverErrorObserver> error_observer,
        const char* filename)
    {
        std::unique_ptr<ProxyResolver> resolver;
        ProxyResolverFactoryV8TracingWrapper factory(
            host_resolver, net_log,
            base::Bind(&ReturnErrorObserver, base::Passed(&error_observer)));
        TestCompletionCallback callback;
        std::unique_ptr<ProxyResolverFactory::Request> request;
        int rv = factory.CreateProxyResolver(LoadScriptData(filename), &resolver,
            callback.callback(), &request);
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());
        EXPECT_TRUE(resolver);
        return resolver;
    }

    class MockErrorObserver : public ProxyResolverErrorObserver {
    public:
        void OnPACScriptError(int line_number, const base::string16& error) override
        {
            output += base::StringPrintf("Error: line %d: %s\n", line_number,
                base::UTF16ToASCII(error).c_str());
            waiter_.NotifyEvent(EVENT_ERROR);
            if (!error_callback_.is_null())
                error_callback_.Run();
        }

        std::string GetOutput()
        {
            return output;
        }

        void RunOnError(const base::Closure& callback)
        {
            error_callback_ = callback;
            waiter_.WaitForEvent(EVENT_ERROR);
        }

    private:
        enum Event {
            EVENT_ERROR,
        };
        std::string output;

        base::Closure error_callback_;
        EventWaiter<Event> waiter_;
    };

    TEST_F(ProxyResolverV8TracingWrapperTest, Simple)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            &log, &host_resolver, base::WrapUnique(error_observer), "simple.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        EXPECT_EQ("foo:99", proxy_info.proxy_server().ToURI());

        EXPECT_EQ(0u, host_resolver.num_resolve());

        // There were no errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- nothing was logged.
        EXPECT_EQ(0u, log.GetSize());
        EXPECT_EQ(0u, request_log.GetSize());
    }

    TEST_F(ProxyResolverV8TracingWrapperTest, JavascriptError)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            &log, &host_resolver, base::WrapUnique(error_observer), "error.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://throw-an-error/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, callback.WaitForResult());

        EXPECT_EQ(0u, host_resolver.num_resolve());

        EXPECT_EQ(
            "Error: line 5: Uncaught TypeError: Cannot read property 'split' "
            "of null\n",
            error_observer->GetOutput());

        // Check the NetLogs -- there was 1 alert and 1 javascript error, and they
        // were output to both the global log, and per-request log.
        TestNetLogEntry::List entries_list[2];
        log.GetEntries(&entries_list[0]);
        request_log.GetEntries(&entries_list[1]);

        for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
            const TestNetLogEntry::List& entries = entries_list[list_i];
            EXPECT_EQ(2u, entries.size());
            EXPECT_TRUE(LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
                NetLog::PHASE_NONE));
            EXPECT_TRUE(LogContainsEvent(entries, 1, NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
                NetLog::PHASE_NONE));

            EXPECT_EQ("{\"message\":\"Prepare to DIE!\"}", entries[0].GetParamsJson());
            EXPECT_EQ(
                "{\"line_number\":5,\"message\":\"Uncaught TypeError: Cannot "
                "read property 'split' of null\"}",
                entries[1].GetParamsJson());
        }
    }

    TEST_F(ProxyResolverV8TracingWrapperTest, TooManyAlerts)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "too_many_alerts.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        // Iteration1 does a DNS resolve
        // Iteration2 exceeds the alert buffer
        // Iteration3 runs in blocking mode and completes
        EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI());

        EXPECT_EQ(1u, host_resolver.num_resolve());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- the script generated 50 alerts, which were mirrored
        // to both the global and per-request logs.
        TestNetLogEntry::List entries_list[2];
        log.GetEntries(&entries_list[0]);
        request_log.GetEntries(&entries_list[1]);

        for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
            const TestNetLogEntry::List& entries = entries_list[list_i];
            EXPECT_EQ(50u, entries.size());
            for (size_t i = 0; i < entries.size(); ++i) {
                ASSERT_TRUE(LogContainsEvent(
                    entries, i, NetLog::TYPE_PAC_JAVASCRIPT_ALERT, NetLog::PHASE_NONE));
            }
        }
    }

    // Verify that buffered alerts cannot grow unboundedly, even when the message is
    // empty string.
    TEST_F(ProxyResolverV8TracingWrapperTest, TooManyEmptyAlerts)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "too_many_empty_alerts.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI());

        EXPECT_EQ(1u, host_resolver.num_resolve());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- the script generated 50 alerts, which were mirrored
        // to both the global and per-request logs.
        TestNetLogEntry::List entries_list[2];
        log.GetEntries(&entries_list[0]);
        request_log.GetEntries(&entries_list[1]);

        for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
            const TestNetLogEntry::List& entries = entries_list[list_i];
            EXPECT_EQ(1000u, entries.size());
            for (size_t i = 0; i < entries.size(); ++i) {
                ASSERT_TRUE(LogContainsEvent(
                    entries, i, NetLog::TYPE_PAC_JAVASCRIPT_ALERT, NetLog::PHASE_NONE));
            }
        }
    }

    // This test runs a PAC script that issues a sequence of DNS resolves. The test
    // verifies the final result, and that the underlying DNS resolver received
    // the correct set of queries.
    TEST_F(ProxyResolverV8TracingWrapperTest, Dns)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRuleForAddressFamily("host1", ADDRESS_FAMILY_IPV4,
            "166.155.144.44");
        host_resolver.rules()->AddIPLiteralRule("host1", "::1,192.168.1.1",
            std::string());
        host_resolver.rules()->AddSimulatedFailure("host2");
        host_resolver.rules()->AddRule("host3", "166.155.144.33");
        host_resolver.rules()->AddRule("host5", "166.155.144.55");
        host_resolver.rules()->AddSimulatedFailure("host6");
        host_resolver.rules()->AddRuleForAddressFamily("*", ADDRESS_FAMILY_IPV4,
            "122.133.144.155");
        host_resolver.rules()->AddRule("*", "133.122.100.200");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            &log, &host_resolver, base::WrapUnique(error_observer), "dns.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        // The test does 13 DNS resolution, however only 7 of them are unique.
        EXPECT_EQ(7u, host_resolver.num_resolve());

        const char* kExpectedResult = "122.133.144.155-" // myIpAddress()
                                      "null-" // dnsResolve('')
                                      "__1_192.168.1.1-" // dnsResolveEx('host1')
                                      "null-" // dnsResolve('host2')
                                      "166.155.144.33-" // dnsResolve('host3')
                                      "122.133.144.155-" // myIpAddress()
                                      "166.155.144.33-" // dnsResolve('host3')
                                      "__1_192.168.1.1-" // dnsResolveEx('host1')
                                      "122.133.144.155-" // myIpAddress()
                                      "null-" // dnsResolve('host2')
                                      "-" // dnsResolveEx('host6')
                                      "133.122.100.200-" // myIpAddressEx()
                                      "166.155.144.44" // dnsResolve('host1')
                                      ":99";

        EXPECT_EQ(kExpectedResult, proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- the script generated 1 alert, mirrored to both
        // the per-request and global logs.
        TestNetLogEntry::List entries_list[2];
        log.GetEntries(&entries_list[0]);
        request_log.GetEntries(&entries_list[1]);

        for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
            const TestNetLogEntry::List& entries = entries_list[list_i];
            EXPECT_EQ(1u, entries.size());
            EXPECT_TRUE(LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
                NetLog::PHASE_NONE));
            EXPECT_EQ("{\"message\":\"iteration: 7\"}", entries[0].GetParamsJson());
        }
    }

    // This test runs a PAC script that does "myIpAddress()" followed by
    // "dnsResolve()". This requires 2 restarts. However once the HostResolver's
    // cache is warmed, subsequent calls should take 0 restarts.
    TEST_F(ProxyResolverV8TracingWrapperTest, DnsChecksCache)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("foopy", "166.155.144.11");
        host_resolver.rules()->AddRule("*", "122.133.144.155");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            &log, &host_resolver, base::WrapUnique(error_observer), "simple_dns.js");

        TestCompletionCallback callback1;
        TestCompletionCallback callback2;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info,
            callback1.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback1.WaitForResult());

        // The test does 2 DNS resolutions.
        EXPECT_EQ(2u, host_resolver.num_resolve());

        // The first request took 2 restarts, hence on g_iteration=3.
        EXPECT_EQ("166.155.144.11:3", proxy_info.proxy_server().ToURI());

        rv = resolver->GetProxyForURL(GURL("http://foopy/req2"), &proxy_info,
            callback2.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback2.WaitForResult());

        EXPECT_EQ(4u, host_resolver.num_resolve());

        // This time no restarts were required, so g_iteration incremented by 1.
        EXPECT_EQ("166.155.144.11:4", proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        EXPECT_EQ(0u, log.GetSize());
        EXPECT_EQ(0u, request_log.GetSize());
    }

    // This test runs a weird PAC script that was designed to defeat the DNS tracing
    // optimization. The proxy resolver should detect the inconsistency and
    // fall-back to synchronous mode execution.
    TEST_F(ProxyResolverV8TracingWrapperTest, FallBackToSynchronous1)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("host1", "166.155.144.11");
        host_resolver.rules()->AddRule("crazy4", "133.199.111.4");
        host_resolver.rules()->AddRule("*", "122.133.144.155");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "global_sideffects1.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        // The script itself only does 2 DNS resolves per execution, however it
        // constructs the hostname using a global counter which changes on each
        // invocation.
        EXPECT_EQ(3u, host_resolver.num_resolve());

        EXPECT_EQ("166.155.144.11-133.199.111.4:100",
            proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- the script generated 1 alert, mirrored to both
        // the per-request and global logs.
        TestNetLogEntry::List entries_list[2];
        log.GetEntries(&entries_list[0]);
        request_log.GetEntries(&entries_list[1]);

        for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
            const TestNetLogEntry::List& entries = entries_list[list_i];
            EXPECT_EQ(1u, entries.size());
            EXPECT_TRUE(LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
                NetLog::PHASE_NONE));
            EXPECT_EQ("{\"message\":\"iteration: 4\"}", entries[0].GetParamsJson());
        }
    }

    // This test runs a weird PAC script that was designed to defeat the DNS tracing
    // optimization. The proxy resolver should detect the inconsistency and
    // fall-back to synchronous mode execution.
    TEST_F(ProxyResolverV8TracingWrapperTest, FallBackToSynchronous2)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("host1", "166.155.144.11");
        host_resolver.rules()->AddRule("host2", "166.155.144.22");
        host_resolver.rules()->AddRule("host3", "166.155.144.33");
        host_resolver.rules()->AddRule("host4", "166.155.144.44");
        host_resolver.rules()->AddRule("*", "122.133.144.155");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "global_sideffects2.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        EXPECT_EQ(3u, host_resolver.num_resolve());

        EXPECT_EQ("166.155.144.44:100", proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- nothing was logged.
        EXPECT_EQ(0u, log.GetSize());
        EXPECT_EQ(0u, request_log.GetSize());
    }

    // This test runs a weird PAC script that yields a never ending sequence
    // of DNS resolves when restarting. Running it will hit the maximum
    // DNS resolves per request limit (20) after which every DNS resolve will
    // fail.
    TEST_F(ProxyResolverV8TracingWrapperTest, InfiniteDNSSequence)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("host*", "166.155.144.11");
        host_resolver.rules()->AddRule("*", "122.133.144.155");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "global_sideffects3.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        EXPECT_EQ(20u, host_resolver.num_resolve());

        EXPECT_EQ(
            "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
            "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
            "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
            "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
            "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
            "null:21",
            proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- 1 alert was logged.
        EXPECT_EQ(1u, log.GetSize());
        EXPECT_EQ(1u, request_log.GetSize());
    }

    // This test runs a weird PAC script that yields a never ending sequence
    // of DNS resolves when restarting. Running it will hit the maximum
    // DNS resolves per request limit (20) after which every DNS resolve will
    // fail.
    TEST_F(ProxyResolverV8TracingWrapperTest, InfiniteDNSSequence2)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("host*", "166.155.144.11");
        host_resolver.rules()->AddRule("*", "122.133.144.155");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "global_sideffects4.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        EXPECT_EQ(20u, host_resolver.num_resolve());

        EXPECT_EQ("null21:34", proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        // Check the NetLogs -- 1 alert was logged.
        EXPECT_EQ(1u, log.GetSize());
        EXPECT_EQ(1u, request_log.GetSize());
    }

    void DnsDuringInitHelper(bool synchronous_host_resolver)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        host_resolver.set_synchronous_mode(synchronous_host_resolver);
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("host1", "91.13.12.1");
        host_resolver.rules()->AddRule("host2", "91.13.12.2");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(&log, &host_resolver, base::WrapUnique(error_observer),
            "dns_during_init.js");

        // Initialization did 2 dnsResolves.
        EXPECT_EQ(2u, host_resolver.num_resolve());

        host_resolver.rules()->ClearRules();
        host_resolver.GetHostCache()->clear();

        host_resolver.rules()->AddRule("host1", "145.88.13.3");
        host_resolver.rules()->AddRule("host2", "137.89.8.45");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            callback.callback(), NULL, request_log.bound());
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        // Fetched host1 and host2 again, since the ones done during initialization
        // should not have been cached.
        EXPECT_EQ(4u, host_resolver.num_resolve());

        EXPECT_EQ("91.13.12.1-91.13.12.2-145.88.13.3-137.89.8.45:99",
            proxy_info.proxy_server().ToURI());

        // Check the NetLogs -- the script generated 2 alerts during initialization.
        EXPECT_EQ(0u, request_log.GetSize());
        TestNetLogEntry::List entries;
        log.GetEntries(&entries);

        ASSERT_EQ(2u, entries.size());
        EXPECT_TRUE(LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
            NetLog::PHASE_NONE));
        EXPECT_TRUE(LogContainsEvent(entries, 1, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
            NetLog::PHASE_NONE));

        EXPECT_EQ("{\"message\":\"Watsup\"}", entries[0].GetParamsJson());
        EXPECT_EQ("{\"message\":\"Watsup2\"}", entries[1].GetParamsJson());
    }

    // Tests a PAC script which does DNS resolves during initialization.
    TEST_F(ProxyResolverV8TracingWrapperTest, DnsDuringInit)
    {
        // Test with both both a host resolver that always completes asynchronously,
        // and then again with one that completes synchronously.
        DnsDuringInitHelper(false);
        DnsDuringInitHelper(true);
    }

    void CrashCallback(int)
    {
        // Be extra sure that if the callback ever gets invoked, the test will fail.
        CHECK(false);
    }

    // Start some requests, cancel them all, and then destroy the resolver.
    // Note the execution order for this test can vary. Since multiple
    // threads are involved, the cancellation may be received a different
    // times.
    TEST_F(ProxyResolverV8TracingWrapperTest, CancelAll)
    {
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddSimulatedFailure("*");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js");

        const size_t kNumRequests = 5;
        ProxyInfo proxy_info[kNumRequests];
        ProxyResolver::RequestHandle request[kNumRequests];

        for (size_t i = 0; i < kNumRequests; ++i) {
            int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info[i],
                base::Bind(&CrashCallback), &request[i],
                BoundNetLog());
            EXPECT_EQ(ERR_IO_PENDING, rv);
        }

        for (size_t i = 0; i < kNumRequests; ++i) {
            resolver->CancelRequest(request[i]);
        }
    }

    // Note the execution order for this test can vary. Since multiple
    // threads are involved, the cancellation may be received a different
    // times.
    TEST_F(ProxyResolverV8TracingWrapperTest, CancelSome)
    {
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddSimulatedFailure("*");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js");

        ProxyInfo proxy_info1;
        ProxyInfo proxy_info2;
        ProxyResolver::RequestHandle request1;
        ProxyResolver::RequestHandle request2;
        TestCompletionCallback callback;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info1,
            base::Bind(&CrashCallback), &request1,
            BoundNetLog());
        EXPECT_EQ(ERR_IO_PENDING, rv);

        rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info2,
            callback.callback(), &request2, BoundNetLog());
        EXPECT_EQ(ERR_IO_PENDING, rv);

        resolver->CancelRequest(request1);

        EXPECT_EQ(OK, callback.WaitForResult());
    }

    // Cancel a request after it has finished running on the worker thread, and has
    // posted a task the completion task back to origin thread.
    TEST_F(ProxyResolverV8TracingWrapperTest, CancelWhilePendingCompletionTask)
    {
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddSimulatedFailure("*");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            nullptr, &host_resolver, base::WrapUnique(error_observer), "error.js");

        ProxyInfo proxy_info1;
        ProxyInfo proxy_info2;
        ProxyResolver::RequestHandle request1;
        ProxyResolver::RequestHandle request2;
        TestCompletionCallback callback;

        int rv = resolver->GetProxyForURL(GURL("http://throw-an-error/"),
            &proxy_info1, base::Bind(&CrashCallback),
            &request1, BoundNetLog());
        EXPECT_EQ(ERR_IO_PENDING, rv);

        // Wait until the first request has finished running on the worker thread.
        // Cancel the first request, while it has a pending completion task on
        // the origin thread.
        error_observer->RunOnError(base::Bind(&ProxyResolver::CancelRequest,
            base::Unretained(resolver.get()),
            request1));

        // Start another request, to make sure it is able to complete.
        rv = resolver->GetProxyForURL(GURL("http://i-have-no-idea-what-im-doing/"),
            &proxy_info2, callback.callback(), &request2,
            BoundNetLog());
        EXPECT_EQ(ERR_IO_PENDING, rv);

        EXPECT_EQ(OK, callback.WaitForResult());

        EXPECT_EQ("i-approve-this-message:42", proxy_info2.proxy_server().ToURI());
    }

    // This implementation of HostResolver allows blocking until a resolve request
    // has been received. The resolve requests it receives will never be completed.
    class BlockableHostResolver : public HostResolver {
    public:
        BlockableHostResolver()
            : num_cancelled_requests_(0)
            , waiting_for_resolve_(false)
        {
        }

        int Resolve(const RequestInfo& info,
            RequestPriority priority,
            AddressList* addresses,
            const CompletionCallback& callback,
            RequestHandle* out_req,
            const BoundNetLog& net_log) override
        {
            EXPECT_FALSE(callback.is_null());
            EXPECT_TRUE(out_req);

            if (!action_.is_null())
                action_.Run();

            // Indicate to the caller that a request was received.
            EXPECT_TRUE(waiting_for_resolve_);
            base::MessageLoop::current()->QuitWhenIdle();

            // This line is intentionally after action_.Run(), since one of the
            // tests does a cancellation inside of Resolve(), and it is more
            // interesting if *out_req hasn't been written yet at that point.
            *out_req = reinterpret_cast<RequestHandle*>(1); // Magic value.

            // Return ERR_IO_PENDING as this request will NEVER be completed.
            // Expectation is for the caller to later cancel the request.
            return ERR_IO_PENDING;
        }

        int ResolveFromCache(const RequestInfo& info,
            AddressList* addresses,
            const BoundNetLog& net_log) override
        {
            NOTREACHED();
            return ERR_DNS_CACHE_MISS;
        }

        void CancelRequest(RequestHandle req) override
        {
            EXPECT_EQ(reinterpret_cast<RequestHandle*>(1), req);
            num_cancelled_requests_++;
        }

        void SetAction(const base::Callback<void(void)>& action) { action_ = action; }

        // Waits until Resolve() has been called.
        void WaitUntilRequestIsReceived()
        {
            waiting_for_resolve_ = true;
            base::RunLoop().Run();
            DCHECK(waiting_for_resolve_);
            waiting_for_resolve_ = false;
        }

        int num_cancelled_requests() const { return num_cancelled_requests_; }

    private:
        int num_cancelled_requests_;
        bool waiting_for_resolve_;
        base::Callback<void(void)> action_;
    };

    // This cancellation test exercises a more predictable cancellation codepath --
    // when the request has an outstanding DNS request in flight.
    TEST_F(ProxyResolverV8TracingWrapperTest,
        CancelWhileOutstandingNonBlockingDns)
    {
        BlockableHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js");

        ProxyInfo proxy_info1;
        ProxyInfo proxy_info2;
        ProxyResolver::RequestHandle request1;
        ProxyResolver::RequestHandle request2;

        int rv = resolver->GetProxyForURL(GURL("http://foo/req1"), &proxy_info1,
            base::Bind(&CrashCallback), &request1,
            BoundNetLog());

        EXPECT_EQ(ERR_IO_PENDING, rv);

        host_resolver.WaitUntilRequestIsReceived();

        rv = resolver->GetProxyForURL(GURL("http://foo/req2"), &proxy_info2,
            base::Bind(&CrashCallback), &request2,
            BoundNetLog());

        EXPECT_EQ(ERR_IO_PENDING, rv);

        host_resolver.WaitUntilRequestIsReceived();

        resolver->CancelRequest(request1);
        resolver->CancelRequest(request2);

        EXPECT_EQ(2, host_resolver.num_cancelled_requests());

        // After leaving this scope, the ProxyResolver is destroyed.
        // This should not cause any problems, as the outstanding work
        // should have been cancelled.
    }

    void CancelRequestAndPause(ProxyResolver* resolver,
        ProxyResolver::RequestHandle request)
    {
        resolver->CancelRequest(request);

        // Sleep for a little bit. This makes it more likely for the worker
        // thread to have returned from its call, and serves as a regression
        // test for http://crbug.com/173373.
        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30));
    }

    // In non-blocking mode, the worker thread actually does block for
    // a short time to see if the result is in the DNS cache. Test
    // cancellation while the worker thread is waiting on this event.
    TEST_F(ProxyResolverV8TracingWrapperTest, CancelWhileBlockedInNonBlockingDns)
    {
        BlockableHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js");

        ProxyInfo proxy_info;
        ProxyResolver::RequestHandle request;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            base::Bind(&CrashCallback), &request,
            BoundNetLog());

        EXPECT_EQ(ERR_IO_PENDING, rv);

        host_resolver.SetAction(
            base::Bind(CancelRequestAndPause, resolver.get(), request));

        host_resolver.WaitUntilRequestIsReceived();
    }

    // Cancel the request while there is a pending DNS request, however before
    // the request is sent to the host resolver.
    TEST_F(ProxyResolverV8TracingWrapperTest, CancelWhileBlockedInNonBlockingDns2)
    {
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            nullptr, &host_resolver, base::WrapUnique(error_observer), "dns.js");

        ProxyInfo proxy_info;
        ProxyResolver::RequestHandle request;

        int rv = resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info,
            base::Bind(&CrashCallback), &request,
            BoundNetLog());

        EXPECT_EQ(ERR_IO_PENDING, rv);

        // Wait a bit, so the DNS task has hopefully been posted. The test will
        // work whatever the delay is here, but it is most useful if the delay
        // is large enough to allow a task to be posted back.
        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
        resolver->CancelRequest(request);

        EXPECT_EQ(0u, host_resolver.num_resolve());
    }

    TEST_F(ProxyResolverV8TracingWrapperTest,
        CancelCreateResolverWhileOutstandingBlockingDns)
    {
        BlockableHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        ProxyResolverFactoryV8TracingWrapper factory(
            &host_resolver, nullptr,
            base::Bind(&ReturnErrorObserver,
                base::Passed(base::WrapUnique(error_observer))));

        std::unique_ptr<ProxyResolver> resolver;
        std::unique_ptr<ProxyResolverFactory::Request> request;
        int rv = factory.CreateProxyResolver(LoadScriptData("dns_during_init.js"),
            &resolver, base::Bind(&CrashCallback),
            &request);
        EXPECT_EQ(ERR_IO_PENDING, rv);

        host_resolver.WaitUntilRequestIsReceived();

        request.reset();
        EXPECT_EQ(1, host_resolver.num_cancelled_requests());
    }

    TEST_F(ProxyResolverV8TracingWrapperTest,
        DeleteFactoryWhileOutstandingBlockingDns)
    {
        BlockableHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        std::unique_ptr<ProxyResolver> resolver;
        std::unique_ptr<ProxyResolverFactory::Request> request;
        {
            ProxyResolverFactoryV8TracingWrapper factory(
                &host_resolver, nullptr,
                base::Bind(&ReturnErrorObserver,
                    base::Passed(base::WrapUnique(error_observer))));

            int rv = factory.CreateProxyResolver(LoadScriptData("dns_during_init.js"),
                &resolver, base::Bind(&CrashCallback),
                &request);
            EXPECT_EQ(ERR_IO_PENDING, rv);
            host_resolver.WaitUntilRequestIsReceived();
        }
        EXPECT_EQ(1, host_resolver.num_cancelled_requests());
    }

    TEST_F(ProxyResolverV8TracingWrapperTest, ErrorLoadingScript)
    {
        BlockableHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        ProxyResolverFactoryV8TracingWrapper factory(
            &host_resolver, nullptr,
            base::Bind(&ReturnErrorObserver,
                base::Passed(base::WrapUnique(error_observer))));

        std::unique_ptr<ProxyResolver> resolver;
        std::unique_ptr<ProxyResolverFactory::Request> request;
        TestCompletionCallback callback;
        int rv = factory.CreateProxyResolver(LoadScriptData("error_on_load.js"), &resolver,
            callback.callback(), &request);
        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, callback.WaitForResult());
        EXPECT_FALSE(resolver);
    }

    // This tests that the execution of a PAC script is terminated when the DNS
    // dependencies are missing. If the test fails, then it will hang.
    TEST_F(ProxyResolverV8TracingWrapperTest, Terminate)
    {
        TestNetLog log;
        BoundTestNetLog request_log;
        MockCachingHostResolver host_resolver;
        MockErrorObserver* error_observer = new MockErrorObserver;

        host_resolver.rules()->AddRule("host1", "182.111.0.222");
        host_resolver.rules()->AddRule("host2", "111.33.44.55");

        std::unique_ptr<ProxyResolver> resolver = CreateResolver(
            &log, &host_resolver, base::WrapUnique(error_observer), "terminate.js");

        TestCompletionCallback callback;
        ProxyInfo proxy_info;

        int rv = resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info,
            callback.callback(), NULL, request_log.bound());

        EXPECT_EQ(ERR_IO_PENDING, rv);
        EXPECT_EQ(OK, callback.WaitForResult());

        // The test does 2 DNS resolutions.
        EXPECT_EQ(2u, host_resolver.num_resolve());

        EXPECT_EQ("foopy:3", proxy_info.proxy_server().ToURI());

        // No errors.
        EXPECT_EQ("", error_observer->GetOutput());

        EXPECT_EQ(0u, log.GetSize());
        EXPECT_EQ(0u, request_log.GetSize());
    }

    // Tests that multiple instances of ProxyResolverV8TracingWrapper can coexist
    // and run correctly at the same time. This is relevant because at the moment
    // (time this test was written) each ProxyResolverV8TracingWrapper creates its
    // own thread to run V8 on, however each thread is operating on the same
    // v8::Isolate.
    TEST_F(ProxyResolverV8TracingWrapperTest, MultipleResolvers)
    {
        // ------------------------
        // Setup resolver0
        // ------------------------
        MockHostResolver host_resolver0;
        host_resolver0.rules()->AddRuleForAddressFamily("host1", ADDRESS_FAMILY_IPV4,
            "166.155.144.44");
        host_resolver0.rules()->AddIPLiteralRule("host1", "::1,192.168.1.1",
            std::string());
        host_resolver0.rules()->AddSimulatedFailure("host2");
        host_resolver0.rules()->AddRule("host3", "166.155.144.33");
        host_resolver0.rules()->AddRule("host5", "166.155.144.55");
        host_resolver0.rules()->AddSimulatedFailure("host6");
        host_resolver0.rules()->AddRuleForAddressFamily("*", ADDRESS_FAMILY_IPV4,
            "122.133.144.155");
        host_resolver0.rules()->AddRule("*", "133.122.100.200");
        std::unique_ptr<ProxyResolver> resolver0 = CreateResolver(nullptr, &host_resolver0,
            base::WrapUnique(new MockErrorObserver), "dns.js");

        // ------------------------
        // Setup resolver1
        // ------------------------
        std::unique_ptr<ProxyResolver> resolver1 = CreateResolver(nullptr, &host_resolver0,
            base::WrapUnique(new MockErrorObserver), "dns.js");

        // ------------------------
        // Setup resolver2
        // ------------------------
        std::unique_ptr<ProxyResolver> resolver2 = CreateResolver(nullptr, &host_resolver0,
            base::WrapUnique(new MockErrorObserver), "simple.js");

        // ------------------------
        // Setup resolver3
        // ------------------------
        MockHostResolver host_resolver3;
        host_resolver3.rules()->AddRule("foo", "166.155.144.33");
        std::unique_ptr<ProxyResolver> resolver3 = CreateResolver(nullptr, &host_resolver3,
            base::WrapUnique(new MockErrorObserver), "simple_dns.js");

        // ------------------------
        // Queue up work for each resolver (which will be running in parallel).
        // ------------------------

        ProxyResolver* resolver[] = {
            resolver0.get(),
            resolver1.get(),
            resolver2.get(),
            resolver3.get(),
        };

        const size_t kNumResolvers = arraysize(resolver);
        const size_t kNumIterations = 20;
        const size_t kNumResults = kNumResolvers * kNumIterations;
        TestCompletionCallback callback[kNumResults];
        ProxyInfo proxy_info[kNumResults];

        for (size_t i = 0; i < kNumResults; ++i) {
            size_t resolver_i = i % kNumResolvers;
            int rv = resolver[resolver_i]->GetProxyForURL(
                GURL("http://foo/"), &proxy_info[i], callback[i].callback(), NULL,
                BoundNetLog());
            EXPECT_EQ(ERR_IO_PENDING, rv);
        }

        // ------------------------
        // Verify all of the results.
        // ------------------------

        const char* kExpectedForDnsJs = "122.133.144.155-" // myIpAddress()
                                        "null-" // dnsResolve('')
                                        "__1_192.168.1.1-" // dnsResolveEx('host1')
                                        "null-" // dnsResolve('host2')
                                        "166.155.144.33-" // dnsResolve('host3')
                                        "122.133.144.155-" // myIpAddress()
                                        "166.155.144.33-" // dnsResolve('host3')
                                        "__1_192.168.1.1-" // dnsResolveEx('host1')
                                        "122.133.144.155-" // myIpAddress()
                                        "null-" // dnsResolve('host2')
                                        "-" // dnsResolveEx('host6')
                                        "133.122.100.200-" // myIpAddressEx()
                                        "166.155.144.44" // dnsResolve('host1')
                                        ":99";

        for (size_t i = 0; i < kNumResults; ++i) {
            size_t resolver_i = i % kNumResolvers;
            EXPECT_EQ(OK, callback[i].WaitForResult());

            std::string proxy_uri = proxy_info[i].proxy_server().ToURI();

            if (resolver_i == 0 || resolver_i == 1) {
                EXPECT_EQ(kExpectedForDnsJs, proxy_uri);
            } else if (resolver_i == 2) {
                EXPECT_EQ("foo:99", proxy_uri);
            } else if (resolver_i == 3) {
                EXPECT_EQ("166.155.144.33:",
                    proxy_uri.substr(0, proxy_uri.find(':') + 1));
            } else {
                NOTREACHED();
            }
        }
    }

} // namespace

} // namespace net
